package com.hzdy.master.controller;

import com.hzdy.master.entity.table.CreateTable;
import com.hzdy.master.service.ICreateTableService;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.internal.SessionImpl;
import org.springframework.beans.BeanMetadataAttribute;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.RepositoryController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.config.ResourceMetadataHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.json.JacksonMappingAwareSortTranslator;
import org.springframework.data.rest.webmvc.json.MappingAwareDefaultedPageableArgumentResolver;
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
import org.springframework.data.rest.webmvc.support.DomainClassResolver;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.spi.PersistenceProvider;
import javax.persistence.spi.PersistenceUnitInfo;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.*;

@RestController
@Slf4j
public class CreateTableController {

    @Autowired
    private AnnotationConfigServletWebServerApplicationContext context;

    @Autowired
    @SuppressWarnings("all")
    private LocalContainerEntityManagerFactoryBean entityManagerFactoryMysql;

    @Autowired
    private ICreateTableService createTableService;

    @Resource
    private JpaMetamodelMappingContext jpaMetamodelMappingContext;

    @Resource
    private MongoMappingContext mongoMappingContext;

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private Repositories repositories;

    @Autowired
    private DefaultListableBeanFactory registrars;

    @Autowired
    private RepositoryResourceMappings resourceMetadata;

    @Autowired
    private RepositoryRestConfiguration repositoryRestConfiguration;

    @Autowired
    private RepositoryEntityLinks repositoryEntityLinks;

    @Resource
    private RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter;

    @Resource
    private DelegatingHandlerMapping restHandlerMapping;

    @GetMapping("person")
    public void person() throws IOException, ClassNotFoundException {
        //createTableService.create(1L,"com.hzdy.master","target/classes");
        Map<String, Object> jpaPropertyMap = entityManagerFactoryMysql.getJpaPropertyMap();
        PersistenceUnitInfo persistenceUnitInfo = entityManagerFactoryMysql.getPersistenceUnitInfo();
        persistenceUnitInfo.getManagedClassNames().add("com.hzdy.master.Person");

        PersistenceProvider persistenceProvider = entityManagerFactoryMysql.getPersistenceProvider();

        entityManager.close();
        entityManager = persistenceProvider.createContainerEntityManagerFactory(persistenceUnitInfo, jpaPropertyMap).createEntityManager();
        //BeanUtils.copyProperties(this.entityManager);
        // 创建DDL脚本
        // persistenceProvider.generateSchema(entityManagerFactoryMysql.getPersistenceUnitInfo(),entityManagerFactoryMysql.getJpaPropertyMap());
        // Metadata metadata = MetadataExtractorIntegrator.INSTANCE.getMetadata();
        entityManager.getMetamodel().entity(Class.forName("com.hzdy.master.Person"));
        this.entityManager.getMetamodel();
    }

    @GetMapping("compile")
    public void compile() {
        createTableService.compile("target/classes/com/hzdy/master", "Person.java");
    }
    @GetMapping("create/repository")
    public void createRepository() throws IOException {
        createTableService.createRepository("com.hzdy.master.custom","com.hzdy.master.Person","target/classes");
    }
    @GetMapping("compile/repository")
    public void compileRepository() throws IOException {
        createTableService.compile("target/classes/com/hzdy/master/custom","PersonRepository.java");
    }
    @GetMapping("create")
    public void create() throws IOException {
        createTableService.create(1L,"com.hzdy.master","target/classes");
    }
    @GetMapping("all")
    public void all() throws IOException {
        create();
        compile();
        createRepository();
        compileRepository();
    }
    @GetMapping("add")
    public void add() {
//        entityManager.getTransaction().begin();
//        Person person = new Person();
//        person.setName("郑勇达");
//        entityManager.persist(person);
//        entityManager.getTransaction().commit();
    }

    @GetMapping("context")
    public void context() throws Exception {
        entityManager.getMetamodel().entity(Class.forName("com.hzdy.master.Person"));
        Session session = entityManager.unwrap(Session.class);
        SessionFactory sessionFactory = session.getSessionFactory();
        Class<?> repositoryInterface = org.springframework.util.ClassUtils.forName("com.hzdy.master.custom.PersonRepository", this.context.getClassLoader());
        setEntityManagerFactoryMysql(sessionFactory);

        // 重新设置JpaMetamodelMappingContext
        setJpaMetamodelMappingContext(sessionFactory, repositoryInterface);

        // 重新对仓库接口设置EntityManager和JpaMetamodelMappingContext
        setRepositoryFactoryInfos(repositoryInterface);

//        context.getBean("PersonRepository");
        // 重新设置ResourceMetadata
        setResourceMetadata();
    }

    public static void modify(Object object, String fieldName, Object newFieldValue) throws Exception {
        Field field = object.getClass().getDeclaredField(fieldName);

        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true); //Field 的 modifiers 是私有的
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

        if (!field.isAccessible()) {
            field.setAccessible(true);
        }

        field.set(object, newFieldValue);
    }

    public void setDomainClassResolver(RepositoryResourceMappings mappings) throws Exception {
        for (int i = 0; i < repositoryExporterHandlerAdapter.getArgumentResolvers().size(); i++) {
            HandlerMethodArgumentResolver handlerMethodArgumentResolver = repositoryExporterHandlerAdapter.getArgumentResolvers().get(i);
            if (handlerMethodArgumentResolver instanceof MappingAwareDefaultedPageableArgumentResolver) {
                Field jacksonMappingAwareSortTranslatorField = ((MappingAwareDefaultedPageableArgumentResolver) handlerMethodArgumentResolver).getClass().getDeclaredField("translator");
                jacksonMappingAwareSortTranslatorField.setAccessible(true);
                JacksonMappingAwareSortTranslator jacksonMappingAwareSortTranslator = (JacksonMappingAwareSortTranslator) jacksonMappingAwareSortTranslatorField.get(handlerMethodArgumentResolver);
                Field jacksonDomainClassResolverField = jacksonMappingAwareSortTranslator.getClass().getDeclaredField("domainClassResolver");
                jacksonDomainClassResolverField.setAccessible(true);

                DomainClassResolver domainClassResolver = (DomainClassResolver) jacksonDomainClassResolverField.get(jacksonMappingAwareSortTranslator);
                Field BaseUriField = domainClassResolver.getClass().getDeclaredField("baseUri");
                BaseUriField.setAccessible(true);

                Constructor domainClassResolverConstructor = domainClassResolver.getClass().getDeclaredConstructor(Repositories.class, ResourceMappings.class, BaseUri.class);//获取有参构造
                domainClassResolverConstructor.setAccessible(true);
                modify(jacksonMappingAwareSortTranslator, "domainClassResolver", (DomainClassResolver) domainClassResolverConstructor.newInstance(repositories, mappings, BaseUriField.get(domainClassResolver)));
                break;
            }
        }
    }

    @RequestMapping(value = "getAllUrl", method = RequestMethod.GET)
    public Object getAllUrl() {
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        // 获取url与类和方法的对应信息
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> m : map.entrySet()) {
            Map<String, String> map1 = new HashMap<String, String>();
            RequestMappingInfo info = m.getKey();
            HandlerMethod method = m.getValue();
            PatternsRequestCondition p = info.getPatternsCondition();
            for (String url : p.getPatterns()) {
                map1.put("url", url);
            }
            map1.put("className", method.getMethod().getDeclaringClass().getName()); // 类名
            map1.put("method", method.getMethod().getName()); // 方法名
            RequestMethodsRequestCondition methodsCondition = info.getMethodsCondition();
            for (RequestMethod requestMethod : methodsCondition.getMethods()) {
                map1.put("type", requestMethod.toString());
            }

            list.add(map1);
        }

        JSONArray jsonArray = JSONArray.fromObject(list);

        return jsonArray;

    }

    public void setRestHandlerMapping(RepositoryResourceMappings mappings) throws Exception {
        for (int i = 0; i < restHandlerMapping.getDelegates().size(); i++) {
            HandlerMapping requestMappingHandlerMapping = restHandlerMapping.getDelegates().get(i);
            if (requestMappingHandlerMapping instanceof RepositoryRestHandlerMapping) {
                modify(((RepositoryRestHandlerMapping) requestMappingHandlerMapping), "mappings", mappings);
            }
        }
    }

    public void setJpaMetamodelMappingContext(SessionFactory sessionFactory, Class<?> repositoryInterface) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Set<javax.persistence.metamodel.Metamodel> metadateSet = new HashSet<>();
        metadateSet.add(sessionFactory.getMetamodel());
        Collection typeInformationOptionalMap = jpaMetamodelMappingContext.getPersistentEntities();
        JpaMetamodelMappingContext jpaMetamodelMappingContext = new JpaMetamodelMappingContext(metadateSet);

        Field modelsField = jpaMetamodelMappingContext.getClass().getDeclaredField("models");
        modelsField.setAccessible(true);
        modelsField.set(this.jpaMetamodelMappingContext, modelsField.get(jpaMetamodelMappingContext));

        Method addPersistentEntity = this.jpaMetamodelMappingContext.getClass().getSuperclass().getDeclaredMethod("addPersistentEntity", TypeInformation.class);

        addPersistentEntity.setAccessible(true);

        addPersistentEntity.invoke(this.jpaMetamodelMappingContext, ClassTypeInformation.from(AbstractRepositoryMetadata.getMetadata(repositoryInterface).getDomainType()));

    }

    public void setEntityManagerFactoryMysql(SessionFactory sessionFactory) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
        Field nativeEntityManagerFactoryField = this.entityManagerFactoryMysql.getClass().getSuperclass().getDeclaredField("nativeEntityManagerFactory");
        nativeEntityManagerFactoryField.setAccessible(true);
        nativeEntityManagerFactoryField.set(this.entityManagerFactoryMysql, sessionFactory);
    }

    public void setRepositoryFactoryInfos(Class<?> repositoryInterface) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException {
        Field repositoryFactoryInfosField = repositories.getClass().getDeclaredField("repositoryFactoryInfos");
        repositoryFactoryInfosField.setAccessible(true);
        HashMap<Object, RepositoryFactoryInformation> repositoryFactoryInfos = (HashMap) repositoryFactoryInfosField.get(repositories);

        Field repositoryBeanNamesField = repositories.getClass().getDeclaredField("repositoryBeanNames");
        repositoryBeanNamesField.setAccessible(true);
        HashMap<Object, String> repositoryBeanNames = (HashMap) repositoryBeanNamesField.get(repositories);

        for (Map.Entry<Object, RepositoryFactoryInformation> entiy : repositoryFactoryInfos.entrySet()) {
            Object name = entiy.getKey();
            RepositoryFactoryInformation value = entiy.getValue();
            try {
                if (value instanceof JpaRepositoryFactoryBean) {
                    JpaRepositoryFactoryBean oldJpaRepositoryFactoryBean = (JpaRepositoryFactoryBean) value;
                    Field repositoryInterfaceField = oldJpaRepositoryFactoryBean.getClass().getSuperclass().getSuperclass().getDeclaredField("repositoryInterface");
                    repositoryInterfaceField.setAccessible(true);
                    Class<?> reposiotryInterface = (Class<?>) repositoryInterfaceField.get(oldJpaRepositoryFactoryBean);
                    ((JpaRepositoryFactoryBean) value).setEntityManager(entityManager);
                    ((JpaRepositoryFactoryBean) value).setMappingContext(jpaMetamodelMappingContext);
                    value.toString();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }

        BeanDefinition beanDefinition = registrars.getBeanDefinition("dataElementRepository");
//        beanDefinition.toString();
        ConstructorArgumentValues.ValueHolder constructorArgumentValuesHolder = beanDefinition.getConstructorArgumentValues().getIndexedArgumentValues().get(0);
        constructorArgumentValuesHolder.setValue("com.hzdy.master.custom.PersonRepository");
        BeanMetadataAttribute beanMetadataAttribute = new BeanMetadataAttribute("factoryBeanObjectType", "com.hzdy.master.custom.PersonRepository");
        beanDefinition.setAttribute("factoryBeanObjectType", beanMetadataAttribute);
        beanDefinition.setAttribute("entityManager",this.entityManager);

        registrars.registerBeanDefinition("PersonRepository", beanDefinition);
        Method cacheRepositoryFactory = repositories.getClass().getDeclaredMethod("cacheRepositoryFactory", String.class);
        cacheRepositoryFactory.setAccessible(true);
        //  cacheRepositoryFactory.invoke(repositories, "&PersonRepository");
        RepositoryFactoryInformation dataElementRepositoryFactoryInformation = context.getBean("&dataElementRepository",
                RepositoryFactoryInformation.class);

        Field beanFactoryField = dataElementRepositoryFactoryInformation.getClass().getSuperclass().getSuperclass().getDeclaredField("beanFactory");
        Field classLoaderField = dataElementRepositoryFactoryInformation.getClass().getSuperclass().getSuperclass().getDeclaredField("classLoader");
        beanFactoryField.setAccessible(true);
        classLoaderField.setAccessible(true);
        BeanFactory beanFactory = (BeanFactory) beanFactoryField.get(dataElementRepositoryFactoryInformation);
        ClassLoader classLoader = (ClassLoader) classLoaderField.get(dataElementRepositoryFactoryInformation);


        RepositoryFactoryInformation jpaRepositoryFactoryBean = new JpaRepositoryFactoryBean(repositoryInterface);
        ((JpaRepositoryFactoryBean) jpaRepositoryFactoryBean).setEntityManager(entityManager);
        ((JpaRepositoryFactoryBean) jpaRepositoryFactoryBean).setMappingContext(jpaMetamodelMappingContext);
        beanFactoryField.set(jpaRepositoryFactoryBean, beanFactory);
        classLoaderField.set(jpaRepositoryFactoryBean, classLoader);
        ((JpaRepositoryFactoryBean) jpaRepositoryFactoryBean).afterPropertiesSet();


        Class<?> domainType = ClassUtils
                .getUserClass(jpaRepositoryFactoryBean.getRepositoryInformation().getDomainType());

        RepositoryInformation information = jpaRepositoryFactoryBean.getRepositoryInformation();
        Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
        String beanName = BeanFactoryUtils.transformedBeanName("&PersonRepository");
        Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1);
        typesToRegister.add(domainType);
        typesToRegister.addAll(alternativeDomainTypes);

        for (Class<?> type : typesToRegister) {
            repositoryFactoryInfos.put(type, jpaRepositoryFactoryBean);
            repositoryBeanNames.put(type, beanName);
        }
    }

    public void setResourceMetadata() throws Exception {
        Field cacheField = resourceMetadata.getClass().getSuperclass().getDeclaredField("cache");
        cacheField.setAccessible(true);
        Field resourceMetadataEntitiesField = resourceMetadata.getClass().getSuperclass().getDeclaredField("entities");
        resourceMetadataEntitiesField.setAccessible(true);

        repositoryRestConfiguration.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ALL);
        // 覆盖原有数据
        resourceMetadata = new RepositoryResourceMappings(this.repositories, new PersistentEntities(Arrays.asList(jpaMetamodelMappingContext, mongoMappingContext)), repositoryRestConfiguration);
        // 清空缓存数据
        Map<Object, ResourceMetadata> cache = (Map<Object, ResourceMetadata>) cacheField.get(resourceMetadata);
        //cache.clear();

        RepositoryController repositoryController = context.getBean(RepositoryController.class);

        ResourceMetadataHandlerMethodArgumentResolver resourceMetadataHandlerMethodArgumentResolver = context.getBean(ResourceMetadataHandlerMethodArgumentResolver.class);

        RepositoryResourceMappings repositoryResourceMappings = context.getBean(RepositoryResourceMappings.class);
        repositoryResourceMappings = resourceMetadata;

        setRestHandlerMapping(resourceMetadata);
        setDomainClassResolver(resourceMetadata);
        modify(repositoryController, "mappings", resourceMetadata);
        modify(resourceMetadataHandlerMethodArgumentResolver, "mappings", resourceMetadata);
        modify(repositoryEntityLinks, "mappings", resourceMetadata);
        System.out.println(jpaMetamodelMappingContext.hasPersistentEntityFor(CreateTable.class));
        System.out.println(jpaMetamodelMappingContext.hasPersistentEntityFor(Class.forName("com.hzdy.master.Person")));
    }
}
